------------------------------------------------------------------------------
-- UI module
------------------------------------------------------------------------------

local ui = {}

ui.env = {
  app_ver = 0.1,
  fonts_dir = "assets/fonts",
  images_dir = "assets/images",
  sounds_dir = "assets/sounds",
  fonts_ext = ".otf",
  images_ext = ".png",
  sounds_ext = ".ogg",
  max_width = 1920, -- PNG assets support a max resolution of 1920 x 1080 px
  max_height = 1080,
  grid_unit = 120,  -- Each grid square is 120 px at max resolution
}

ui.colors = {
  yellow = { 255, 245, 133 },
  blue = { 13, 78, 135, },
  teal70 = { 92, 141, 136 },
  teal60 = { 135, 176, 170 },
  teal50 = { 126, 191, 180 },
  teal30 = { 156, 215, 205 },
  teal20 = { 197, 229, 218 },
  teal10 = { 237, 251, 255 },
  magenta = { 224, 86, 84 },  -- Menu, die button, game result modal colors
  green = { 36, 189, 73 },
  emerald = { 26, 115, 116 },
  white = { 255, 255, 255 },  -- Text color
  orange = { 248, 163, 0 },   -- Game result overlay colors
  mint = { 154, 255, 206 },
}

ui.assets = {
  images = {},
  fonts = {},
  sounds = {},
}

ui.state = {
  debug = true,
  scale = 1200 / ui.env.max_width,  -- window width / max width
  mouse_pos = {},
  mouse_hover = { false },
  mouse_press = { false },
  cur_scene = "title_menu",
  prev_scene = nil,
  scene_changed = false,
  cur_player = nil,
  cur_player_turn = "yellow",
  game_active = false,
  game_result = nil,
  dice_count = { yellow = 4, blue = 4 },
  story_modal_open = true,
}


------------------------------------------------------------------------------
-- Utility functions
------------------------------------------------------------------------------

ui.utils = {}


-- Format debug output
function ui.utils.debug(tag, msg)
  if ui.state.debug then
    print("[" .. tag .. "] " .. msg)
  end
end


-- Given a pair of position coordinates ({ x, y }) and a range ({ min_x, min_y,
-- max_x, max_y }), check if the position is within the bounds of the min and
-- max points inclusively. Return true if within range, false otherwise.
function ui.utils.in_range(pos, range)
  if pos[1] >= range[1] and pos[1] <= range[3] and
    pos[2] >= range[2] and pos[2] <= range[4] then
    return true
  else
    return false
  end
end


------------------------------------------------------------------------------
-- Basic functions
------------------------------------------------------------------------------

-- Initialise assets from a directory of assets files
function ui.init_assets(dir, asset_type)
  -- Get a list of files and load them into the assets table
  -- [TODO] The use of `ls` is probably not cross-platform compatible.
  local files_str = io.popen("ls " .. dir)
  -- Abort if no files found
  if files_str == nil then do return end end
  if asset_type == "images" then
    for f in string.gmatch(files_str:read("*a"), "%S+") do
      if string.find(f, ui.env.images_ext) ~= nil then
        ui.assets.images[string.gsub(f, ui.env.images_ext, "")] =
          love.graphics.newImage(dir .. "/" .. f)
      end
    end

  elseif asset_type == "fonts" then
    for f in string.gmatch(files_str:read("*a"), "%S+") do
      if string.find(f, ui.env.fonts_ext) ~= nil then
        ui.assets.fonts[string.gsub(f, ui.env.fonts_ext, "")] =
          love.graphics.newFont(dir .. "/" .. f)
      end
    end

  elseif asset_type == "sounds" then
    for f in string.gmatch(files_str:read("*a"), "%S+") do
      if string.find(f, ui.env.sounds_ext) ~= nil then
        ui.assets.fonts[string.gsub(f, ui.env.sounds_ext, "")] =
          love.sound.newDecoder(dir .. "/" .. f)
      end
    end

  end
end


-- Return an alpha-adjusted value of a rgba color
function ui.alpha(col, a)
  local color_mod = {}
  -- Support pre-defined rgb colors
  if type(col) == "string" then color_mod = ui.colors[col] end
  color_mod[4] = a
  -- Default to opacity of 1
  if a == nil then color_mod[4] = 1 end
  -- Convert a rgb color to LÖVE color range
  for c = 1, #color_mod do
    if color_mod[c] > 1 then
      color_mod[c] = color_mod[c] / 255
    end
  end
  return color_mod
end

-- Return the size in pixels relative to the grid unit
function ui.units(size)
  return math.floor(ui.env.grid_unit * size * ui.state.scale)
end


-- Return the coordinates to center an image
function ui.center_pos(img)
  local pos = {}
  pos[1] = math.floor(((ui.env.max_width / 2) -
    (ui.assets.images[img]:getWidth() / 2)) * ui.state.scale)
  pos[2] = math.floor(((ui.env.max_height / 2) -
    (ui.assets.images[img]:getHeight() / 2)) * ui.state.scale)
  return pos
end


-- Convert the original size of an image to the scaled size
function ui.get_size(img)
  return { math.floor(ui.assets.images[img]:getWidth() * ui.state.scale),
    math.floor(ui.assets.images[img]:getHeight() * ui.state.scale) }
end


-- Wrapper around love.graphics.draw with scale factor included
function ui.draw(img, x, y, rot)
  if rot == nil then rot = 0 end
  love.graphics.draw(ui.assets.images[img], math.floor(x), math.floor(y), rot,
    ui.state.scale, ui.state.scale)
end


-- Wrapper around love.graphics.font with the scale factor included
function ui.print(str, x, y, font, size, col, align, align_width)
  -- [TODO] How to change the font size of an already preloaded font face
  -- without re-initialising? The path is also probably not cross-platform
  -- compatible.
  love.graphics.setNewFont(ui.env.fonts_dir .. "/" .. font .. ui.env.fonts_ext,
    math.floor(size * ui.state.scale))
  -- Default color is white
  if col == nil then
    love.graphics.setColor(ui.alpha("white"))
  else
    love.graphics.setColor(ui.alpha(col))
  end
  if (align ~= nil) and (align_width ~= nil) then
    love.graphics.printf(str, math.floor(x), math.floor(y), align_width,
      align)
  else
    love.graphics.print(str, math.floor(x), math.floor(y))
  end
  love.graphics.setColor(1, 1, 1)
end


-- Tile an image over a rectangular area
function ui.tile(img, x, y, width, height)
  local pos = { x, y }
  for hi = 1, math.ceil(height / (ui.assets.images[img]:getHeight() *
      ui.state.scale)) do
    pos[1] = 0
    for wi = 1, math.ceil(width / (ui.assets.images[img]:getWidth() *
      ui.state.scale)) do
      ui.draw(img, pos[1], pos[2])
      pos[1] = pos[1] + ui.get_size(img)[1]
    end
    pos[2] = pos[2] + ui.get_size(img)[2]
  end
end


-- Set the mouse cursor in hover state
function ui.set_cursor()
  if ui.state.mouse_hover[1] then
    local hand = love.mouse.getSystemCursor("hand")
    love.mouse.setCursor(hand)
  else
    local arrow = love.mouse.getSystemCursor("arrow")
    love.mouse.setCursor(arrow)
  end
end


------------------------------------------------------------------------------
-- Blocks
------------------------------------------------------------------------------

-- Blocks are groups of UI components used in scenes.
ui.blocks = {}


-- Tiled background
function ui.blocks.bg(img)
  ui.tile(img, 0, 0, ui.units(16), ui.units(9))
end


-- Colorized menu buttons in normal and hover states
function ui.blocks.menu_buttons(btns)
  local col = ""
  local label_col = ""
  for b, r in pairs(btns) do
    if ui.state.mouse_hover[2] == b then
      col = "yellow"
      label_col = col
    else
      col = "blue"
      label_col = "white"
    end
    love.graphics.setColor(ui.alpha(col, 1))
    ui.draw(btns[b][5], btns[b][1], btns[b][2])
    love.graphics.setColor(1, 1, 1)
    ui.print(b, btns[b][3], btns[b][4], "calcutta-regular", 26, label_col)
  end
end


-- Title scene game logo
function ui.blocks.title_logo()
  ui.draw("title-menu-logo", ui.units(0.5),
    ui.center_pos("title-menu-logo")[2])
end


-- Title scene main menu
function ui.blocks.title_menu()
  local btns = {
    new = { ui.units(10.4), ui.center_pos("title-menu-new")[2], ui.units(10.3),
      ui.units(5), "title-menu-new" },
    load = { ui.units(11.3), ui.center_pos("title-menu-load")[2],
      ui.units(11.4), ui.units(5), "title-menu-load" },
    help = { ui.units(12.4), ui.center_pos("title-menu-help")[2],
      ui.units(12.5), ui.units(5), "title-menu-help" },
    settings = { ui.units(13.6), ui.center_pos("title-menu-settings")[2],
      ui.units(13.5), ui.units(5), "title-menu-settings" },
    quit = { ui.units(14.8), ui.center_pos("title-menu-quit")[2],
      ui.units(14.9), ui.units(5), "title-menu-quit" },
  }
  ui.blocks.menu_buttons(btns)
end


-- Title scene new player selection menu
function ui.blocks.title_player_menu()
  ui.print("play as", ui.units(11.6), ui.units(3.7), "calcutta-regular", 32)
  -- Player color buttons
  local img = "title-menu-player"
  local col = ""
  local label_col = ""
  local player_btns = {
    yellow = { ui.units(10.8), 0, ui.units(10.8), ui.units(5) },
    blue = { ui.units(12.5), 0, ui.units(12.6), ui.units(5) },
  }
  local player_select = {
    yellow = { ui.units(10.68), 0 },
    blue = { ui.units(12.38), 0 },
  }
  for b, r in pairs(player_btns) do
    col = b
    if ui.state.mouse_hover[2] == b then
      ui.draw("title-menu-player-selected", player_select[b][1],
        ui.center_pos("title-menu-player-selected")[2])
      label_col = b
    else
      label_col = "white"
    end
    love.graphics.setColor(ui.alpha(col, 1))
    ui.draw(img, player_btns[b][1], ui.center_pos(img)[2])
    love.graphics.setColor(1, 1, 1)
    ui.print(b, player_btns[b][3], player_btns[b][4], "calcutta-regular", 26,
      label_col)
  end
  -- Back button
  local back_btn = {
    back = { ui.units(14.8), ui.center_pos("title-menu-back")[2],
      ui.units(14.7), ui.units(5), "title-menu-back" }
  }
  ui.blocks.menu_buttons(back_btn)
end


function ui.blocks.die_tray()
  if ui.state.cur_player_turn ~= nil then
    ui.draw("die-tray-" .. ui.state.cur_player_turn, ui.units(0.3),
      ui.units(0.3))
  end
end


function ui.blocks.dice_count()
  if ui.state.dice_count.yellow >= 1 then
    local die_pos_x = ui.units(2.25)
    for d = 1, ui.state.dice_count.yellow do
      ui.draw("dice-count-yellow", die_pos_x, ui.units(0.3))
      die_pos_x = die_pos_x + ui.get_size("dice-count-yellow")[1] +
        ui.units(0.1)
    end
  end
  if ui.state.dice_count.blue >= 1 then
    die_pos_x = ui.units(2.25)
    for d = 1, ui.state.dice_count.blue do
      ui.draw("dice-count-blue", die_pos_x, ui.units(0.57))
      die_pos_x = die_pos_x + ui.get_size("dice-count-blue")[1] + ui.units(0.1)
    end
  end
end


function ui.blocks.story_modal()
  if ui.state.story_modal_open then
    -- Story modal
    love.graphics.setColor(ui.alpha("teal50", 0.8))
    love.graphics.rectangle("fill", ui.units(12), 0, ui.units(4), ui.units(9))
    love.graphics.setColor(1, 1, 1)
    -- Modal nav
    ui.draw("modal-arrow-up", ui.units(13.8), ui.units(0.4))
    ui.draw("modal-menu", ui.units(14.9), ui.units(0.3))
  else
    ui.draw("nav-arrow-down", ui.units(13.8), ui.units(0.4))
    ui.draw("nav-menu", ui.units(14.9), ui.units(0.3))
  end
end


function ui.blocks.game_result(result)
  -- [TODO] Replace placeholder modal messages
  local overlay_col = ""
  local modal_img = "modal-result-draw-yellow"
  local modal_pos_y = ui.center_pos(modal_img)[2] - ui.units(0.3)
  local modal_msg = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
    .. " sed do eiusmod tempor incididunt ut labore."
  if result[1] == "win" and result[2] == "yellow" then
    overlay_img = "overlay-result-yellow"
    modal_img = "modal-result-yellow"
    modal_pos_y = ui.center_pos(modal_img)[2] - ui.units(0.5)
    modal_title = "Summer has arrived"
    -- Image overlay
    ui.draw(overlay_img, ui.center_pos(overlay_img)[1],
      ui.center_pos(overlay_img)[2])
  -- Win, blue
  elseif result[1] == "win" and result[2] == "blue" then
    overlay_img = "overlay-result-blue"
    modal_img = "modal-result-blue"
    modal_title = "Winter has arrived"
    -- Image overlay
    ui.draw(overlay_img, ui.center_pos(overlay_img)[1],
      ui.center_pos(overlay_img)[2])
  elseif result[1] == "draw" and result[2] == "yellow" then
    overlay_col = "mint"
    modal_img = "modal-result-draw-yellow"
    modal_title = "Spring has arrived"
    -- Overlay
    love.graphics.setColor(ui.alpha(overlay_col, 0.5))
    love.graphics.rectangle("fill", 0, 0, ui.units(16), ui.units(9))
    love.graphics.setColor(1, 1, 1)
  elseif result[1] == "draw" and result[2] == "blue" then
    overlay_col = "orange"
    modal_img = "modal-result-draw-blue"
    modal_title = "Autumn has arrived"
    -- Overlay
    love.graphics.setColor(ui.alpha(overlay_col, 0.5))
    love.graphics.rectangle("fill", 0, 0, ui.units(16), ui.units(9))
    love.graphics.setColor(1, 1, 1)
  else
    ui.utils.debug("game_result", "unknown result.")
  end
  -- Main menu nav button
  ui.draw("nav-menu", ui.units(14.9), ui.units(0.3))
  -- Modal
  ui.draw(modal_img, ui.center_pos(modal_img)[1], modal_pos_y)
  ui.print(modal_title, ui.units(6), ui.units(4), "calcutta-bold", 52,
    "emerald", "center", ui.units(4))
  ui.print(modal_msg, ui.units(5.5), ui.units(4.6), "calcutta-regular", 26,
    "emerald", "center", ui.units(5))
  ui.print("play again", ui.units(6), ui.units(5.2), "calcutta-semibold", 26,
    "magenta", "center", ui.units(4))
end


-- Game scene main menu
function ui.blocks.game_menu()
  -- Translucent overlay
  love.graphics.setColor(ui.alpha("teal60", 0.5))
  love.graphics.rectangle("fill", 0, 0, ui.units(12), ui.units(9))
  love.graphics.setColor(1, 1, 1)
  -- Modal
  love.graphics.setColor(ui.alpha("teal60", 0.8))
  love.graphics.rectangle("fill", ui.units(12), 0, ui.units(4), ui.units(9))
  love.graphics.setColor(1, 1, 1)
  ui.draw("modal-menu", ui.units(14.9), ui.units(0.3))
  -- Menu buttons
  local btns = {
    new = { ui.units(13.1), ui.units(2.3), ui.units(13), ui.units(3),
      "title-menu-new" },
    load = { ui.units(14.4), ui.units(2.3), ui.units(14.48), ui.units(3),
      "title-menu-load" },
    help = { ui.units(12.9), ui.units(4.3), ui.units(13), ui.units(5),
      "title-menu-help" },
    settings = { ui.units(14.4), ui.units(4.3), ui.units(14.3), ui.units(5),
      "title-menu-settings" },
    quit = { ui.units(13.7), ui.units(6.2), ui.units(13.8), ui.units(6.9),
      "title-menu-quit" },
  }
  ui.blocks.menu_buttons(btns)
end


function ui.blocks.help()
  -- [TODO] Add gameplay instructions
  ui.blocks.bg("title-menu-tile")
  -- Heading
  love.graphics.setColor(ui.alpha("blue", 1))
  ui.draw("title-menu-help", ui.center_pos("title-menu-help")[1],
    ui.units(0.6))
  love.graphics.setColor(1, 1, 1)
  ui.print("how to play", ui.units(6), ui.units(1.5), "calcutta-regular", 52,
  -- Nav
    "white", "center", ui.units(4))
  local btns = {
    about = { ui.units(0.6), ui.units(0.6), ui.units(0.63), ui.units(1.3),
      "title-menu-about" },
    back = { ui.units(14.8), ui.units(0.6), ui.units(14.75), ui.units(1.3),
      "title-menu-back" },
  }
  ui.blocks.menu_buttons(btns)
end


function ui.blocks.about()
  ui.blocks.bg("title-menu-tile")
  -- Heading
  love.graphics.setColor(ui.alpha("blue", 1))
  ui.draw("title-menu-about", ui.center_pos("title-menu-about")[1],
    ui.units(0.6))
  love.graphics.setColor(1, 1, 1)
  ui.print("about", ui.units(6), ui.units(1.4), "calcutta-regular", 52,
    "white", "center", ui.units(4))
  -- Nav
  local btns = {
    help = { ui.units(0.6), ui.units(0.6), ui.units(0.7), ui.units(1.3),
      "title-menu-help" },
    back = { ui.units(14.8), ui.units(0.6), ui.units(14.75), ui.units(1.3),
      "title-menu-back" },
  }
  ui.blocks.menu_buttons(btns)
  -- Contents
  ui.print("mako " .. tostring(ui.env.app_ver) .. " ©2022 43beans",
    ui.units(4), ui.units(2.3), "calcutta-regular", 30, "white", "center",
    ui.units(8))
  ui.print("Credits", ui.units(4), ui.units(3.1), "calcutta-regular", 46,
    "white", "center", ui.units(8))
  ui.print("Story", ui.units(4), ui.units(3.8), "calcutta-bold",
    32, "white", "center", ui.units(8))
  ui.print("acdw", ui.units(4), ui.units(4.1), "calcutta-regular", 30,
    "white", "center", ui.units(8))
  ui.print("dozens", ui.units(4), ui.units(4.4), "calcutta-regular", 30,
    "white", "center", ui.units(8))
  ui.print("Art", ui.units(4), ui.units(5), "calcutta-bold", 32, "white",
    "center", ui.units(8))
  ui.print("mio", ui.units(4), ui.units(5.3), "calcutta-regular", 30,
    "white", "center", ui.units(8))
  ui.print("Sound", ui.units(4), ui.units(5.9), "calcutta-bold", 32,
    "white", "center", ui.units(8))
  ui.print("agafnd", ui.units(4), ui.units(6.2), "calcutta-regular", 30,
    "white", "center", ui.units(8))
  ui.print("Programming", ui.units(4), ui.units(6.8), "calcutta-bold", 32,
    "white", "center", ui.units(8))
  ui.print("marcus", ui.units(4), ui.units(7.1), "calcutta-regular", 30,
    "white", "center", ui.units(8))
  ui.print("wsinatra", ui.units(4), ui.units(7.4), "calcutta-regular", 30,
    "white", "center", ui.units(8))
end


function ui.blocks.settings()
  ui.blocks.bg("title-menu-tile")
  -- Heading
  love.graphics.setColor(ui.alpha("blue", 1))
  ui.draw("title-menu-settings", ui.center_pos("title-menu-settings")[1],
    ui.units(0.6))
  love.graphics.setColor(1, 1, 1)
  ui.print("settings", ui.units(6), ui.units(1.4), "calcutta-regular", 52,
    "white", "center", ui.units(4))
  -- Nav
  local back_btn = {
    back = { ui.units(14.8), ui.units(0.6), ui.units(14.75), ui.units(1.3),
      "title-menu-back" },
  }
  ui.blocks.menu_buttons(back_btn)
  -- Settings
  ui.print("Music", ui.units(6.5), ui.units(2.7), "calcutta-regular", 40,
    "white", "left", ui.units(3.5))
  ui.draw("mode-cancel", ui.units(8), ui.units(2.74))
  ui.draw("mode-confirm", ui.units(8.7), ui.units(2.74))
  ui.print("Sfx", ui.units(6.5), ui.units(3.7), "calcutta-regular", 40,
    "white", "left", ui.units(3.5))
  ui.draw("mode-cancel", ui.units(8), ui.units(3.74))
  ui.draw("mode-confirm", ui.units(8.7), ui.units(3.74))
  ui.print("Dice", ui.units(6.5), ui.units(4.7), "calcutta-regular", 40,
    "white", "left", ui.units(3.5))
  love.graphics.draw(ui.assets.images["dice-1-4"], ui.units(8), ui.units(4.65),
    0, ui.state.scale * 0.5, ui.state.scale * 0.5)
  love.graphics.draw(ui.assets.images["dice-2-4"], ui.units(8.9),
    ui.units(4.65), 0, ui.state.scale * 0.5, ui.state.scale * 0.5)
end


------------------------------------------------------------------------------
-- Scenes
------------------------------------------------------------------------------

ui.scenes = {}


function ui.scenes.title_menu()
  ui.blocks.bg("title-menu-tile")
  ui.blocks.title_logo()
  ui.blocks.title_menu()
end

function ui.scenes.title_player_menu()
  ui.blocks.bg("title-menu-tile")
  ui.blocks.title_logo()
  ui.blocks.title_player_menu()
end

function ui.scenes.game()
  ui.blocks.bg("board-tile")
  ui.blocks.die_tray()
  ui.blocks.dice_count()
  ui.blocks.story_modal()
end

function ui.scenes.game_result()
  ui.scenes.game()
  ui.blocks.game_result(ui.state.game_result)
end


function ui.scenes.game_menu()
  -- Preserve game result visuals
  if ui.state.game_result ~= nil then
    ui.scenes.game_result()
  elseif ui.state.game_result == nil then
    ui.scenes.game()
  end
  ui.blocks.game_menu()
end


function ui.scenes.help()
  ui.blocks.help()
end


function ui.scenes.about()
  ui.blocks.about()
end


function ui.scenes.settings()
  ui.blocks.settings()
end


function ui.scenes.draw(scn)
  ui.scenes[scn]()
end


------------------------------------------------------------------------------
-- Events
------------------------------------------------------------------------------

ui.events = {}


-- Event maps of scene areas that trigger events/state changes
ui.events.maps = {
  title_menu = {
    new = { ui.units(10), ui.units(4), ui.units(11), ui.units(5.2) },
    load = { ui.units(11), ui.units(4), ui.units(12), ui.units(5.2) },
    help = { ui.units(12.3), ui.units(4), ui.units(13.3), ui.units(5.2) },
    settings = { ui.units(13.3), ui.units(4), ui.units(14.3), ui.units(5.2) },
    quit = { ui.units(14.3), ui.units(4), ui.units(15.3), ui.units(5.2) },
  },
  title_player_menu = {
    yellow = { ui.units(10.8), ui.units(4), ui.units(11.8), ui.units(5.2) },
    blue = { ui.units(12.3), ui.units(4), ui.units(13.3), ui.units(5.2) },
    back = { ui.units(14.3), ui.units(4), ui.units(15.3), ui.units(5.2) },
  },
  game = {
    roll = {ui.units(0.5), ui.units(0.5), ui.units(1.8), ui.units(1.8) },
    story = { ui.units(13.8), ui.units(0.4), ui.units(14.1), ui.units(0.6) },
    menu = { ui.units(14.9), ui.units(0.3), ui.units(15.3), ui.units(1.7) },
  },
  game_menu = {
    menu = { ui.units(14.9), ui.units(0.3), ui.units(15.3), ui.units(1.7) },
    new = { ui.units(13), ui.units(2.3), ui.units(14), ui.units(3.3) },
    load = { ui.units(14.3), ui.units(2.3), ui.units(15.3), ui.units(3.3) },
    help = { ui.units(13), ui.units(4.2), ui.units(14), ui.units(5.2) },
    settings = { ui.units(14.3), ui.units(4.2), ui.units(15.3),
      ui.units(5.2) },
    quit = { ui.units(13.7), ui.units(6.1), ui.units(14.7), ui.units(7.1) },
  },
  game_result = {
    menu = { ui.units(14.9), ui.units(0.3), ui.units(15.3), ui.units(1.7) },
    new = { ui.units(7.5), ui.units(5.2), ui.units(8.5), ui.units(5.5) },
  },
  help = {
    about = { ui.units(0.6), ui.units(0.6), ui.units(1.4), ui.units(1.6) },
    back = { ui.units(14.8), ui.units(0.6), ui.units(15.2), ui.units(1.6) },
  },
  about = {
    help = { ui.units(0.6), ui.units(0.6), ui.units(1.4), ui.units(1.6) },
    back = { ui.units(14.8), ui.units(0.6), ui.units(15.2), ui.units(1.6) },
  },
  settings = {
    back = { ui.units(14.8), ui.units(0.6), ui.units(15.2), ui.units(1.6) },
    music_off = { ui.units(7.9), ui.units(2.7), ui.units(8.4), ui.units(3.2) },
    music_on = { ui.units(8.6), ui.units(2.7), ui.units(9.1), ui.units(3.2) },
    sfx_off = { ui.units(7.9), ui.units(3.8), ui.units(8.4), ui.units(4.3) },
    sfx_on = { ui.units(8.6), ui.units(3.8), ui.units(9.1), ui.units(4.3)},
    dice_1 = { ui.units(7.9), ui.units(4.5), ui.units(8.5), ui.units(5.1) },
    dice_2 = { ui.units(8.9), ui.units(4.5), ui.units(9.5), ui.units(5.1) },
  },
}


-- Reset the mouse position to the center of the window after some scene
-- changes. This is to avoid accidentally pressing a button in the same
-- position of a previous scene after the scene changed quickly.
function ui.events.reset_mouse_scene_change()
  if ((ui.state.cur_scene == "title_player_menu") and
    (ui.state.mouse_press[3] == "back")) or
    ((ui.state.cur_scene == "help") and
    (ui.state.mouse_press[3] == "about") or
    (ui.state.mouse_press[3] == "back") ) or
    ((ui.state.cur_scene == "about") and
    (ui.state.mouse_press[3] == "help")) or
    ((ui.state.cur_scene == "settings") and
    (ui.state.mouse_press[3] == "back")) then
    love.mouse.setPosition(ui.units(16) / 2, ui.units(9) / 2)
  end
end


function ui.events.listen_mouse_hover(pos, map)
  for btn, range in pairs(ui.events.maps[map]) do
    if ui.utils.in_range(pos, range) then
      ui.state.mouse_hover = { true, btn }
      do return end
    else
      ui.state.mouse_hover = { false }
    end
  end
end


function ui.events.listen_mouse_press(pos, mouse_btn, map)
  for btn, range in pairs(ui.events.maps[map]) do
    -- UI button pressed with left mouse button
    if (ui.utils.in_range(pos, range)) and (mouse_btn == 1) then
      ui.utils.debug("mouse_press",  "mouse_btn: " .. mouse_btn .. " btn: " ..
        btn .. " x: " .. tostring(pos[1]) .. " y: " .. tostring(pos[2]))
      ui.state.mouse_press = { true, mouse_btn, btn }
      ui.events.reset_mouse_scene_change()
      do return end
    else
      ui.utils.debug("mouse_press",  "mouse_btn: " .. mouse_btn .. " x: " ..
        tostring(pos[1]) .. " y: " .. tostring(pos[2]))
      ui.state.mouse_press = { true, mouse_btn }
    end
  end
end


function ui.events.change_scene()
  -- Detect if scene has changed
  if (ui.state.cur_scene ~= ui.state.prev_scene) and
    (ui.state.prev_scene ~= nil) then
    ui.utils.debug("scene_changed", "prev: " .. ui.state.prev_scene ..
    " cur: " .. ui.state.cur_scene)
    ui.state.scene_changed = true
    ui.state.prev_scene = ui.state.cur_scene
  else
    ui.state.scene_changed = false
  end

  if (ui.state.cur_scene == "title_menu") or
    (ui.state.cur_scene == "game_menu") then
    if ui.state.mouse_press[3] == "new" then
      -- [TODO] Do a full game state reset
      -- Reset the game active and player selection flags for both new and load
      -- scenes, options may be selected from the active game menu
      ui.state.game_active = false
      ui.state.cur_player = nil
      ui.state.cur_scene = "title_player_menu"
    elseif ui.state.mouse_press[3] == "load" then
      -- [TODO] Call system file browser to load game save
      ui.state.game_active = false
      ui.state.cur_player = nil
    elseif ui.state.mouse_press[3] == "help" then
      ui.state.cur_scene = "help"
    elseif ui.state.mouse_press[3] == "settings" then
      ui.state.cur_scene = "settings"
    elseif ui.state.mouse_press[3] == "quit" then
      os.exit()
    end

  elseif ui.state.cur_scene == "title_player_menu" then
    if (ui.state.mouse_press[3] == "yellow") or
      (ui.state.mouse_press[3] == "blue") then
      ui.state.cur_scene = "game"
      ui.state.cur_player = ui.state.mouse_press[3]
      ui.state.game_active = true
    elseif ui.state.mouse_press[3] == "back" then
      ui.state.cur_scene = "title_menu"
    end

  elseif (ui.state.cur_scene == "help") or (ui.state.cur_scene == "about") then
    if ui.state.mouse_press[3] == "about" then
      ui.state.cur_scene = "about"
    elseif ui.state.mouse_press[3] == "help" then
      ui.state.cur_scene = "help"
    -- Return to game if there is an ongoing game, or go back to title menu
    elseif (ui.state.mouse_press[3] == "back") and (ui.state.game_active) then
      ui.state.cur_scene = "game"
    elseif (ui.state.mouse_press[3] == "back") and (not ui.state.game_active)
      then
      ui.state.cur_scene = "title_menu"
    end

  elseif ui.state.cur_scene == "settings" then
    if (ui.state.mouse_press[3] == "back") and (ui.state.game_active) then
      ui.state.cur_scene = "game"
    elseif (ui.state.mouse_press[3] == "back") and (not ui.state.game_active)
      then
      ui.state.cur_scene = "title_menu"
    end
  end

  if ui.state.cur_scene == "game" then
    if ui.state.mouse_press[3] == "menu" then
      ui.state.cur_scene = "game_menu"
    -- Toggle story modal
    elseif ui.state.mouse_press[3] == "story" and
      (not ui.state.story_modal_open) then
      ui.state.story_modal_open = true
    elseif (ui.state.mouse_press[3] == "story") and (ui.state.story_modal_open)
      then
      ui.state.story_modal_open = false
    end

  elseif ui.state.cur_scene == "game_result" then
    local modal = { ui.units(5), ui.units(3.3), ui.units(11), ui.units(5.6) }
    if ui.state.mouse_press[3] == "menu" then
      ui.state.cur_scene = "game_menu"
    elseif ui.state.mouse_press[3] == "new" then
      ui.state.cur_scene = "title_player_menu"
    -- Dismiss the game result modal and reset the result when mouse is pressed
    -- outside the modal.
    elseif (ui.state.mouse_press[1]) and
      (not ui.utils.in_range(ui.state.mouse_pos, modal)) then
      ui.state.cur_scene = "game"
      ui.state.game_result = nil
    end

  elseif ui.state.cur_scene == "game_menu" then
    if (ui.state.mouse_press[3] == "menu") and (ui.state.game_result ~= nil)
      then
      ui.state.cur_scene = "game_result"
    elseif ui.state.mouse_press[3] == "menu" and (ui.state.game_result == nil)
      then
      ui.state.cur_scene = "game"
    end
  end

  -- Reset mouse press after responding
  ui.state.mouse_press = { false }
end


------------------------------------------------------------------------------
-- Callback
------------------------------------------------------------------------------

function love.load()
  ui.init_assets(ui.env.images_dir, "images")
  ui.init_assets(ui.env.fonts_dir, "fonts")
  -- ui.init_assets(ui.env.sounds_dir, "sounds")
end


function love.update()
  ui.state.mouse_pos = { love.mouse.getPosition() }
  ui.events.listen_mouse_hover(ui.state.mouse_pos, ui.state.cur_scene)
  ui.events.change_scene()
end


function love.draw()
  ui.scenes.draw(ui.state.cur_scene)
  ui.set_cursor()
end


function love.mousepressed(x, y, btn, is_touch)
  ui.events.listen_mouse_press(ui.state.mouse_pos, btn, ui.state.cur_scene)
end
